package it.softphone.commons.service.security.shiro.realm;

import static it.softphone.commons.util.LogUtil.logger;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;

public abstract class AbstractSimpleAuthorizingRealm<U> extends AuthorizingRealm {

	public AbstractSimpleAuthorizingRealm() {
	}

	public AbstractSimpleAuthorizingRealm(CacheManager cacheManager) {
		super(cacheManager);
	}

	public AbstractSimpleAuthorizingRealm(CredentialsMatcher matcher) {
		super(matcher);
	}

	public AbstractSimpleAuthorizingRealm(CacheManager cacheManager,	CredentialsMatcher matcher) {
		super(cacheManager, matcher);
	}
	
	protected void raiseIncorrectCredentialsException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new IncorrectCredentialsException(message);
	}

	protected void raiseUnknownAccountException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new UnknownAccountException(message);
	}

	protected void raiseDisabledAccountException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new DisabledAccountException( message);
	}

	protected void raiseExcessiveAttemptsException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new ExcessiveAttemptsException(message);
	}

	protected void raiseExpiredCredentialsException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new org.apache.shiro.authc.ExpiredCredentialsException(message);
	}

	protected void raiseConcurrentAccessException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new org.apache.shiro.authc.ConcurrentAccessException(message);
	}

	protected void raiseLockedAccountException( String message) throws AuthenticationException {
		logger.warn(message);
		throw new org.apache.shiro.authc.LockedAccountException(message);
	}

	@Override
	protected final AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals ) {
		logger.trace("doGetAuthorizationInfo");
		
		SimpleAuthorizationInfo si = principals.oneByType(SimpleAuthorizationInfo.class);
		
		if( si==null ) {
			final String message = "SimpleAuthorizationInfo in principal collection is null!.";
			logger.error( message );
			throw new AuthenticationException(message);
		}
		java.util.Set<String> roles = si.getRoles();
		
		if( roles!=null && !roles.isEmpty() ) return si;
		
    	U user = (U) principals.oneByType( getUserClass() );
		if( user==null ) {
			final String message = "User not found in principal collection!!.";
			logger.error( message );
			throw new AuthenticationException(message);
		}
    	
    	doGetAuthorizationUser( user, si );

    	return si;
	}

	@Override
	protected final AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		logger.trace("doGetAuthenticationInfo");

		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		String username = upToken.getUsername();
		char[] pwd = upToken.getPassword();

		SimplePrincipalCollection c = new SimplePrincipalCollection();

		U user = doGetAuthorizationUser( username, pwd, c );

		SimpleAuthorizationInfo cacheAuthosizationInfo =  new SimpleAuthorizationInfo();

		c.add(username, getName());
		c.add( user, getName());
		c.add( cacheAuthosizationInfo, getName() );

		return new SimpleAuthenticationInfo(c, pwd);
	}

	/**
	 * override to create User object from credentials
	 * 
	 * @param userName
	 * @param pwd
	 * @param principals principal collection
	 * @return
	 * @throws AuthenticationException
	 */
	protected U doGetAuthorizationUser( String userName, char[] pwd , SimplePrincipalCollection principals ) throws AuthenticationException {
		return doGetAuthorizationUser(userName, pwd);
	}
	
	/**
	 * create User object from credentials
	 * 
	 * use {@link #doGetAuthorizationUser(String, char[], SimplePrincipalCollection)} instead
	 * 
	 * @param userName
	 * @param pwd
	 * @return
	 * @throws AuthenticationException
	 */
	protected abstract U doGetAuthorizationUser( String userName, char[] pwd ) throws AuthenticationException;

	/**
	 * add authorization infos (i.e. roles & permissions)
	 * 
	 * @param user
	 * @param si
	 */
	protected abstract void doGetAuthorizationUser(U user, SimpleAuthorizationInfo si) ;

 	/**
	 * give user class information
     *
	 * @return user class
	 */
	protected abstract Class<U> getUserClass();

}
